#include "xsRendPCH.hpp"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <dx/d3dx9.h>
#include <dx/d3d9.h>
#include <dx/DXErr.h>
#include <Shellapi.h>

#include "rend_RendererImpl.h"

#include "rend_FontImpl.h"
#include "rend_ParticleSystemImpl.h"
#include "rend_SpriteImpl.h"

void CreateWnd(HWND &hwnd, int width, int height, const wchar_t *caption, bool fullscreen);
xs::Event g_event;
namespace xs
{

//=================================================================================================
// create_engine
//=========

Renderer* create_engine(unsigned int width, unsigned int height, bool fullscreen, 
						const wchar_t *caption, bool antialiasing)
{
	RendererImpl *xse = new RendererImpl;
	xse->init(0, width, height, fullscreen, caption, antialiasing);
	return xse;
}

//=================================================================================================
// create_engine_ex
//=========

Renderer* create_engine_ex(bool _antialiasing, void* hwnd)
{
	RendererImpl *xse = new RendererImpl;
	xse->init(hwnd,800,600,false,L"",_antialiasing);
	return xse;
}

//=================================================================================================
// RendererImpl::init
//=========

void RendererImpl::init(void* hwnd, unsigned int _width, unsigned int _height, 
						bool _fullscreen, const wchar_t *caption, bool _antialiasing)
{
	antialiasing = _antialiasing;
	fullscreen = _fullscreen;

	// ustaw okno
	if(hwnd == 0)
		CreateWnd(hWindow, _width, _height, caption, fullscreen);
	else
		hWindow = (HWND)hwnd;

	xs::LOG(LL_INF) << "Create window "<<_width<<"x"<<_height<<std::endl;

	// uruchom directa
	D3D = Direct3DCreate9(D3D_SDK_VERSION);

	ZeroMemory(&d3dpp, sizeof(d3dpp));

	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dpp.Windowed = !fullscreen;
	d3dpp.BackBufferCount = 0;
	d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
	d3dpp.EnableAutoDepthStencil = true;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
	d3dpp.hDeviceWindow = hWindow;
	d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

	if(antialiasing)
		d3dpp.MultiSampleType = D3DMULTISAMPLE_4_SAMPLES;

	if(fullscreen)
	{
		d3dpp.BackBufferWidth   = _width;
		d3dpp.BackBufferHeight  = _height;
	}

	D3D_SAFE(D3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWindow, 
		D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &device));

	xs::LOG(LL_INF) << "Create D3DDevice" << std::endl;

	// zrob miejsce dla tekstury debug
	textures.resize(1);
	texture_desc.resize(1);

	// poustawiaj domyslne wartosci
	on_reset_device();

	// zarezerwuj miejsce na vertexy...
	m_vertices.reserve(6*8*1024);
	// ...i indexy
	m_indices.reserve(4*8*1024);
}

//=================================================================================================
// RendererImpl::on_reset_device
//=========

void RendererImpl::on_reset_device()
{
	// ustaw antialiasing
	if(antialiasing)
	{
		D3D_SAFE(device->SetRenderState(D3DRS_MULTISAMPLEANTIALIAS, TRUE));
	}
	else
	{
		D3D_SAFE(device->SetRenderState(D3DRS_MULTISAMPLEANTIALIAS, FALSE));
	}

	// ustaw stany renderingu
	D3D_SAFE(device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE));
	D3D_SAFE(device->SetRenderState(D3DRS_LIGHTING, 0));

	// pobierz back buffer
	D3D_SAFE(device->GetRenderTarget(0, &back_buffer));

	// ustaw stany samplera 0
	D3D_SAFE(device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT));
	D3D_SAFE(device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT));
	D3D_SAFE(device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT));

	D3D_SAFE(device->SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_MIRROR )); 
	D3D_SAFE(device->SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_MIRROR ));

	// ustaw stany batchingu...
	m_tex_filter = TF_POINT;
	m_current_blend = Blends::NONE;
	m_current_texture = -1;
	// ...i wykasuj bufory
	m_vertices.clear();
	m_indices.clear();

	// stworz teksture debug
	D3DXCreateTexture(device, 1, 1, 0, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &textures[0]);
	D3DLOCKED_RECT rect;
	D3D_SAFE(textures[0]->LockRect(0, &rect, 0, 0));
	(*(unsigned long*)rect.pBits) = 0xff00ff00;
	D3D_SAFE(textures[0]->UnlockRect(0));

	LOG(LL_INF) << "Reset device" <<std::endl;
}

//=================================================================================================
// RendererImpl::drop
//=========

void RendererImpl::drop()
{
	// usun tekstury
	for(unsigned int i=0; i<textures.size(); i++)
	{
		if(textures[i]) 
			textures[i]->Release();
	}
	textures.clear();

	// usun render targety
	for(unsigned int i=0; i<render_targets.size(); i++)
		render_targets[i].surf->Release();
	render_targets.clear();

	// zwolnij directa
	D3D_SAFE(device->Release());
	D3D_SAFE(D3D->Release());

	// przywroc ustawienia ekranu
	if(fullscreen)
		ChangeDisplaySettings(0,0);

	LOG(LL_INF) << "Clear everything" << std::endl;

	// skasuj ten obiekt
	delete this;
}

//=================================================================================================
// RendererImpl::get_event
//=========

bool RendererImpl::get_event(Event &event)
{
	// dzialanie komunikatow:
	// funkcja WndProc odbiera komunikat od windowsa i zapisuje go do
	// globalnej zmiennej g_event, ktora potem wpada do zmiennej event

	MSG msg;
	if(PeekMessage(&msg, 0, 0,0, PM_REMOVE))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);

		memcpy(&event, &g_event, sizeof(xs::Event));

		return true;
	}
	return false;
}

//=================================================================================================
// RendererImpl::begin_scene
//=========

bool RendererImpl::begin_scene(const xs::Color &color, const RenderTarget &rt)
{
	// w razie straconego urzadzenia...
	if(lost_device)
	{
		// ...sprawdz, czy moze juz je odzyskano...
		HRESULT res = device->TestCooperativeLevel();
		// ...jesli tak to
		if(res == D3DERR_DEVICENOTRESET)
		{
			// skasuj tekstury
			for(unsigned int i=0; i<textures.size(); i++)
			{
				if(textures[i])
				{ 
					textures[i]->Release(); 
					textures[i]=0;
				}
			}
			// skasuj render targety
			for(unsigned int i=0; i<render_targets.size(); i++)
				render_targets[i].surf->Release();
			back_buffer->Release();
			// zresetuj urzadzenie
			D3D_SAFE(device->Reset(&d3dpp));
			lost_device = false;
			// zaladuj tekstury
			for(unsigned int i=1; i<textures.size(); i++)
			{
				if(texture_desc[i].empty==false)
				{
					if(texture_desc[i].is_render_target == false)
					{
						load_texture(texture_desc[i].filename.c_str(), i);
					}
					else
					{
						IDirect3DSurface9*surf;
						IDirect3DTexture9 *tex;
						HRESULT res = D3DXCreateTexture(device, texture_desc[i].width, texture_desc[i].height, 0,D3DUSAGE_RENDERTARGET,D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &tex);
						tex->GetSurfaceLevel(0, &surf);
						render_targets[texture_desc[i].rt].surf = surf;

						textures[i] = tex;
					}
				}
			}
			// poustawiaj domyslne wartosci
			on_reset_device();

		} else // ...a jesli nie odzyskano - wyjdz
			return false;
	}

	// ustaw render target
	if(rt>=0) 
	{
		D3D_SAFE(device->SetRenderTarget(0, render_targets[rt].surf));
		current_is_back_buffer = false;
	}
	else // mniejszy od zera = back buffer
	{
		D3D_SAFE(device->SetRenderTarget(0, back_buffer));
		current_is_back_buffer = true;
	}

	D3D_SAFE(device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, color, 1.0f, 0));
	D3D_SAFE(device->BeginScene());
	return true;
}

//=================================================================================================
// RendererImpl::end_scene
//=========

bool RendererImpl::end_scene()
{
	if(lost_device)
		return false;
	// wyrenderuj to co zostalo
	render_batch();

	D3D_SAFE(device->EndScene());

	// jesli renderujemy do back buffera
	if(current_is_back_buffer)
	{
		// to narysuj w oknie i sprawdz czy przypadkiem nie stracilismy urzadzenia
		if(D3DERR_DEVICELOST == device->Present(0,0,hWindow,0))
		{
			lost_device = true;
			LOG(LL_INF) << "Lost device" << std::endl;
			return false;
		}
	}

	return true;
}


//=================================================================================================
// RendererImpl::show_cursor
//=========

void RendererImpl::show_cursor(bool show)
{
	ShowCursor(show);
}

//=================================================================================================
// RendererImpl::set_window_caption
//=========

void RendererImpl::set_window_caption(const wchar_t* name)
{
	SetWindowText(hWindow, name);
}

//=================================================================================================
// RendererImpl::load_texture
//=========

void RendererImpl::load_texture(const wchar_t *filename, const Texture &tex_id)
{
	// skasuj stara zawartosc
	if(textures[tex_id]) remove_texture(tex_id);

	// wczytaj plik tekstury z dysku
	File file;
	load_file(filename, file);

	// wczytywanie nie powiodlo sie
	if(file.get_size() == 0)
	{
		empty_texture_slots.push_back(tex_id);
		return;
	}

	// wczytaj teksture z pamieci
	IDirect3DTexture9* texture = 0;
	D3D_SAFE(D3DXCreateTextureFromFileInMemory(device, file.get_data(), file.get_size(), &texture));

	// pobierz info o teksturze
	D3DSURFACE_DESC dsd;
	D3D_SAFE(texture->GetLevelDesc(0, &dsd));

	// dodaj teksture i jej opis do wektorow
	textures[tex_id] = texture;
	texture_description desc;
	desc.is_render_target = false;
	desc.filename = filename;
	desc.empty = false;
	desc.width = dsd.Width;
	desc.height = dsd.Height;
	texture_desc[tex_id] = desc;

	// oraz do zaladowanych tekstur
	loaded_textures[filename] = tex_id;
}

//=================================================================================================
// RendererImpl::get_texture
//=========

Texture RendererImpl::get_texture(const wchar_t *filename)
{
	if(loaded_textures[filename]==0) // jeszcze nie zaladowana
	{
		// to laduj i zwroc
		unsigned int empty_slot = first_empty_texture_slot();

		load_texture(filename, empty_slot);

		LOG(LL_INF) << "Loaded texture "<<wstr_to_str(filename)<<std::endl;

		return empty_slot;
	}

	// byla juz zaladowana, wiec zwroc
	return loaded_textures[filename];
}

//=================================================================================================
// RendererImpl::remove_texture
//=========

void RendererImpl::remove_texture(const xs::Texture &id)
{
	// jesli niepoprawny id to koncz
	if(textures.size()<=id || textures[id]==0 || id==0) return;

	// zwolnij teksture
	textures[id]->Release();
	textures[id] = 0;
	empty_texture_slots.push_back(id);
	texture_desc[id].empty = true;
	loaded_textures[texture_desc[id].filename]=0;
}

//=================================================================================================
// RendererImpl::create_render_target
//=========

RenderTarget RendererImpl::create_render_target(int width, int height)
{
	unsigned int empty_texture = first_empty_texture_slot();

	// stworz i dodaj render target
	render_target_t q;
	q.surf = 0;
	q.texture = empty_texture;
	IDirect3DTexture9 *tex;
	D3D_SAFE(D3DXCreateTexture(device, width, height, 0,D3DUSAGE_RENDERTARGET,D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &tex)); 
	tex->GetSurfaceLevel(0,&q.surf);
	render_targets.push_back(q);

	// dodaj teksture tego render targetu
	textures[empty_texture] = tex;
	texture_description desc;
	desc.is_render_target = true;
	desc.width = width;
	desc.height = height;
	desc.empty = false;
	desc.rt = render_targets.size()-1;
	texture_desc[empty_texture] = desc;

	LOG(LL_INF) << "Create render target "<<width <<"x"<<height<<std::endl;

	return render_targets.size()-1;
}

//=================================================================================================
// RendererImpl::get_render_target_texture
//=========

Texture RendererImpl::get_render_target_texture(RenderTarget render_target)
{
	// jesli niepoprawny render target, to zwroc domyslna teksture
	if(render_target>=(s32)render_targets.size() || render_target<0) 
		return 0;
	// zwroc teksture dla danego render targetu
	return render_targets[render_target].texture;
}

//=================================================================================================
// RendererImpl::first_empty_texture_slot
//=========

unsigned int RendererImpl::first_empty_texture_slot()
{
	// generuje pierwsze wolne miejsce dla nowej tekstury
	if(!empty_texture_slots.empty())
	{
		unsigned int empty_slot = empty_texture_slots[empty_texture_slots.size()-1];
		empty_texture_slots.pop_back();
		return empty_slot;
	}
	textures.push_back(0);
	texture_description desc;
	desc.empty = true;
	texture_desc.push_back(desc);
	return textures.size()-1;
}

//=================================================================================================
// RendererImpl::set_projection
//=========

void RendererImpl::set_projection(float width, float height)
{
	// rysuj co bylo dodane wczesniej
	render_batch();

	// zmien projekcje
	D3DXMATRIX tmp;
	D3DXMatrixOrthoOffCenterLH(&tmp, -width/2, width/2, height/2, -height/2, 0.0f, 1.0f);
	D3D_SAFE(device->SetTransform(D3DTS_PROJECTION, &tmp));
}

//=================================================================================================
// RendererImpl::look_at
//=========

void RendererImpl::look_at(float x, float y, float rotation)
{
	// rysuj co bylo dodane wczesniej
	render_batch();

	D3DXMATRIX rot, trans, out;

	// ustaw rotacje i translacje
	D3DXMatrixRotationZ(&rot, rotation*DEGTORAD);
	D3DXMatrixTranslation(&trans, -x, -y, 0);

	// polacz rotacje i translacje
	D3DXMatrixMultiply(&out, &trans, &rot);
	// zmien widok
	D3D_SAFE(device->SetTransform(D3DTS_VIEW, &out));
}

//=================================================================================================
// RendererImpl::get_texture_size
//=========

void RendererImpl::get_texture_size(const Texture &texture, int &width, int &height)
{
	// jesli niepoprawna tekstura do nic nie rob
	if(texture>=textures.size() || textures[texture]==0 || texture_desc[texture].empty)
		return;
	// zwroc rozmiary tekstury
	width = texture_desc[texture].width;
	height = texture_desc[texture].height;
}

//=================================================================================================
// RendererImpl::render_batch
//=========

void RendererImpl::render_batch()
{
	// jesli nie ma co rysowac to koncz
	if(m_current_texture == -1 || m_vertices.size()==0) 
		return;

	// jesli niepoprawna tekstura to ustaw teksture debug
	if(m_current_texture >= textures.size() || m_current_texture<0 || !textures[m_current_texture])
		m_current_texture = 0;

	// ustaw blending
	if(m_current_blend!=Blends::NONE)
	{
		D3D_SAFE(device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE));
		D3D_SAFE(device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD));
	}
	if(m_current_blend==Blends::ADD)
	{
		D3D_SAFE(device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE));
		D3D_SAFE(device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE));

		D3D_SAFE(device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE));
	} else if(m_current_blend==Blends::ALPHA)
	{
		D3D_SAFE(device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE));
		D3D_SAFE(device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA));
		D3D_SAFE(device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA));
	}

	// ustaw texture i format vertexow
	const unsigned long VERTEX = (D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_DIFFUSE);
	D3D_SAFE(device->SetTexture(0, textures[m_current_texture]));
	D3D_SAFE(device->SetFVF(VERTEX));

	// rysuj
	D3D_SAFE(device->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, m_vertices.size(), m_vertices.size()/2, &m_indices[0], D3DFMT_INDEX32, &m_vertices[0], sizeof(Vertex)));

	// ustaw domyslne
	if(m_current_blend!=Blends::NONE)
	{
		D3D_SAFE(device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE));
		D3D_SAFE(device->SetRenderState(D3DRS_ZWRITEENABLE, TRUE));
	}

	// kasuj bufory
	m_vertices.clear();
	m_indices.clear();
}

//=================================================================================================
// RendererImpl::scissor
//=========

void RendererImpl::scissor(int x, int y, int width, int height)
{
	// rysuj co bylo dodane wczesniej
	render_batch();

	// ustaw scissor rect
	RECT r;
	r.left = x;
	r.top = y;
	r.right = x+width;
	r.bottom = y+height;
	device->SetScissorRect(&r);
	device->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
}

//=================================================================================================
// RendererImpl::set_texture_filter
//=========

void RendererImpl::set_texture_filter(u32 filter)
{
	if(filter == m_tex_filter) return;

	// rysuj co bylo dodane wczesniej
	render_batch();

	m_tex_filter = filter;

	// ustaw ten filter dla wszystkich typow
	D3D_SAFE(device->SetSamplerState(0, D3DSAMP_MAGFILTER, filter));
	D3D_SAFE(device->SetSamplerState(0, D3DSAMP_MINFILTER, filter));
	D3D_SAFE(device->SetSamplerState(0, D3DSAMP_MIPFILTER, filter));
}

//=================================================================================================
// RendererImpl::create_sprite
//=========

SpritePtr RendererImpl::create_sprite(const Texture &texture)
{
	SpriteImpl *sprite = new SpriteImpl(this);
	sprite->set_texture(texture);

	return SpritePtr(sprite);
}

//=================================================================================================
// RendererImpl::create_particle_system
//=========

ParticleSystem* RendererImpl::create_particle_system(const ParticleSystemDesc &desc)
{
	return new ParticleSystemImpl(desc, this);
}

//=================================================================================================
// RendererImpl::create_font
//=========

FontPtr RendererImpl::create_font(const wchar_t* name)
{
	return FontPtr(new FontImpl(name, this));
}

//=================================================================================================
// RendererImpl::render_quad
//=========

void RendererImpl::render_quad(const Quad &quad)
{
	// rysuj dodane wczesniej jesli trzeba
	if(quad.texture!=m_current_texture || quad.blend!=m_current_blend)
	{
		render_batch();

		m_current_texture = quad.texture;
		m_current_blend = quad.blend;
	}

	// dodaj do buforow
	for(int i=0; i<4; i++)
	{
		m_vertices.push_back(quad.v[i]);
	}
	const int add = m_vertices.size()-4;
	m_indices.push_back(add + 0);
	m_indices.push_back(add + 1);
	m_indices.push_back(add + 2);
	m_indices.push_back(add + 0);
	m_indices.push_back(add + 2);
	m_indices.push_back(add + 3);
}

}